
// Copyright (C) 2009-2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;

using Derm.OpenGL;

namespace Derm.Render
{
	/// <summary>
	/// Abstract rendered attribute buffers.
	/// </summary>
	public abstract class BaseShaderAttributes : IShaderState
	{
		#region Attributes

		/// <summary>
		/// Current attributes bindings.
		/// </summary>
		public abstract IEnumerable<string> Bindings { get; }

		/// <summary>
		/// Render vertices attributes
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for rendering.
		/// </param>
		/// <param name="sProgram">
		/// A <see cref="ShaderProgram"/> used for rendering.
		/// </param>
		public abstract void Render(RenderContext ctx, ShaderProgram sProgram);

		#endregion

		#region Automatic Attributes Conditionals

		/// <summary>
		/// Obtain a symbols list depending on current attributes.
		/// </summary>
		/// <remarks>
		/// <para>
		/// Most of ShaderPrograms can handle a different set of attributes. This mean that the
		/// shaders composing the ShaderProgram can be adapted depending on the current available
		/// attributes.
		/// 
		/// This is usefull in the case it's needed to run the ShaderProgram with different 
		/// </para>
		/// <para>
		/// Instead of leave to shader developer the effort of defining preprocessor symbols to
		/// include/exclude certain shader code depending on current inputs, the ShaderAttributes
		/// offers this routine to obtain a list of preprocessor strings dependent on current
		/// available attributes.
		/// 
		/// The defined symbols have the for "HasSymbolName", where symbol name is derived by
		/// removing the first 3 letters of the input binding name. This because suppose
		/// input bindings starts with 2 letters and one underscore (i.e.: ds_VertexPosition will
		/// produce HasVertexPosition symbol).
		/// </para>
		/// </remarks>
		public IEnumerable<string> Defines
		{
			get {
				IEnumerable<string> bindings = Bindings;
				List<string> attrDefines = new List<string>();

				// Append a preprocessor symbol for each defined input
				foreach (string b in bindings) {
					if (mExcludedAttributes.Contains(b) == false)
						attrDefines.Add("Has" + b.Substring(3));
				}

				return (attrDefines);
			}
		}

		/// <summary>
		/// Exclude an attribute for being excluded in rendering.
		/// </summary>
		/// <param name="binding">
		/// A <see cref="System.String"/> indicating the binding name used for
		/// the excluded attribute.
		/// </param>
		public void ExcludeAttribute(string binding)
		{
			Debug.Assert(binding != null);

			// Register excluded attribute
			mExcludedAttributes.Add(binding);
		}

		/// <summary>
		/// Reset excluded attributes.
		/// </summary>
		public void ClearExcludedAttributes()
		{
			mExcludedAttributes.Clear();
		}

		/// <summary>
		/// Check whether an attribute is excluded.
		/// </summary>
		/// <param name="binding"></param>
		/// <returns></returns>
		protected bool IsExcludedAttribute(string binding) { return (mExcludedAttributes.Contains(binding)); }

		/// <summary>
		/// Excluded (but allocated) attributes.
		/// </summary>
		private List<string> mExcludedAttributes = new List<string>();

		#endregion

		#region IShaderState

		/// <summary>
		/// 
		/// </summary>
		public virtual IEnumerable<ShaderObject> Attachments { get { return (null); } }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="ctx"></param>
		/// <param name="sProgram"></param>
		public virtual void SetState(RenderContext ctx, ShaderProgram sProgram) { }

		/// <summary>
		/// Compiler event.
		/// </summary>
		public event CompilerEventHandler CompilerEvent;

		/// <summary>
		/// Linker event.
		/// </summary>
		public event LinkerEventHandler LinkerEvent;

		#endregion
	}

	/// <summary>
	/// Rendered attribute buffers.
	/// </summary>
	/// <remarks>
	/// This class is dedicated to supply data to ShaderProgram instances, and execute
	/// ShaderProgram instances based on the input set.
	/// </remarks>
	public class ShaderAttributes : BaseShaderAttributes
	{
		#region Constructors

		/// <summary>
		/// Construct a ShaderAttributes.
		/// </summary>
		/// <remarks>
		/// By default, the rendering mode in Primitive.Points, the vertices count is 0, and the offset is 0.
		/// </remarks>
		public ShaderAttributes()
		{
			
		}

		/// <summary>
		/// ShaderAttributes constructor.
		/// </summary>
		/// <param name="pMode">
		/// A <see cref="Primitive"/> indicating vertices attributes layout, and the resulting
		/// primitive from rendering.
		/// </param>
		/// <remarks>
		/// By default, the vertices count is 0, and the offset is 0.
		/// </remarks>
		public ShaderAttributes(Primitive pMode) : this(pMode, 0)
		{
			
		}

		/// <summary>
		/// RenderBuffers constructor.
		/// </summary>
		/// <param name="pMode">
		/// A <see cref="Primitive"/> indicating vertices attributes layout, and the resulting
		/// primitive from rendering.
		/// </param>
		/// <param name="count">
		/// A <see cref="System.Int32"/> indicating the number of vertices attributes used
		/// in rendering.
		/// </param>
		/// <remarks>
		/// By default, the offset is 0.
		/// </remarks>
		public ShaderAttributes(Primitive pMode, int count)
		{
			Debug.Assert(count >= 0);

			// Set draw mode
			DrawMode = pMode;
			// Set other attributes
			mCount = count;
		}

		/// <summary>
		/// Destruct ShaderAttributes resources.
		/// </summary>
		/// <remarks>
		/// 
		/// </remarks>
		~ShaderAttributes()
		{
			// Unreference all attributes
			ResetAttributes();
		}

		#endregion

		#region Attributes List

		/// <summary>
		/// Set a shader attribute.
		/// </summary>
		/// <param name="attr">
		/// A <see cref="BufferObject"/> holding the attribute data.
		/// </param>
		/// <remarks>
		/// Uses <paramref name="attr"/> for defining attribute named <param name="binding"/>.
		/// </remarks>
		public void SetAttribute(string binding, BufferObject attr)
		{
			Debug.Assert(String.IsNullOrEmpty(binding) == false);
			Debug.Assert(attr != null);

			// Remove previous attribute (if any)
			ResetAttribute(binding);
			// Register attribute
			mAttributes[binding] = new InputBinding(binding, attr);
			mAttributesChanged = true;
		}

		/// <summary>
		/// Get a shader attribute.
		/// </summary>
		/// <param name="binding">
		/// </param>
		/// <returns>
		/// </returns>
		public BufferObject GetAttribute(string binding)
		{
			if (mAttributes.ContainsKey(binding) == true)
				return (mAttributes[binding].Attribute);
			else
				return (null);
		}

		/// <summary>
		/// Remove a shader attribute.
		/// </summary>
		/// <param name="binding">
		/// </param>
		public void ResetAttribute(string binding)
		{
			// Remove attribute
			mAttributes.Remove(binding);
			mAttributesChanged = true;
		}

		/// <summary>
		/// Remove all shader attributes.
		/// </summary>
		public void ResetAttributes()
		{
			// Clear attribute list
			mAttributes.Clear();
			mAttributesChanged = true;
		}

		/// <summary>
		/// Allocate attribute buffers.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for allocating shader attributes.
		/// </param>
		/// <remarks>
		/// <para>
		/// This routine is used to allocate the shader attributes on server side, allowing the
		/// shader program to use attributes as input. Different allocation strategies can be
		/// defined.
		/// </para>
		/// <para>
		/// It possible to allocate each attribute in separate buffer objects. This strategy allow
		/// to share different buffers in multiple ShaderAttribute instances, mixing them as needed.
		/// Using this kind of allocation ease the modification at runtime of the buffer objects.
		/// Rendering performances can be degraded since shaders shall fetch data from different
		/// buffer objects allocated in different memory addresses.
		/// </para>
		/// <para>
		/// It possible to allocate every attribute in an unique buffer object, packing
		/// sequentially each attribute buffer. This minimize the buffer object allocation, and minimize
		/// server memory fragmentation. Using this kind of allocation ease the modification at runtime
		/// of the attribute buffer, as the previous allocation kind, since the attribute elements
		/// are packed sequentially.
		/// Rendering performances can be degraded since shaders shall fetch data from different
		/// buffer objects allocated in different memory addresses.
		/// </para>
		/// <para>
		/// It possible to allocate every attribute in an unique buffer object, interleaving
		/// each attribute element. This minimize the buffer object allocation, and minimize
		/// server memory fragmentation. Instead, acessing to the single attribute elements
		/// could be difficoult since different attribute elements are packed.
		/// Rendering performance are optimal, since shader fetch contigous data for each vertex,
		/// since attributes are interleaved.
		/// </para>
		/// </remarks>
		private void AllocateAttributes(RenderContext ctx)
		{
			if		(mInterleaveBuffers == true) {
				List<BufferObject> pObjects = new List<BufferObject>(mAttributes.Count);
				long iInterleaveOffset = 0, iInterleaveStride = 0;
				uint iTotalSize = 0;

				/* Align input bindings */
				foreach (KeyValuePair<string, InputBinding> k in mAttributes) {
					InputBinding iBinding = k.Value;

					// Collect objects
					pObjects.Add(iBinding.Attribute);
					// Computed total interleaved buffer size
					iTotalSize += (uint)iBinding.Attribute.Size;
					// Compute stride
					iInterleaveStride += iBinding.Attribute.DataBaseTypeSize*iBinding.Attribute.DataBaseArrayLenth;
				}
				// Pack attributes
				mPackedAttributes = new PackedArrayBufferObject(BufferObject.UsageMode.Draw, BufferObject.XferMode.Static);
				mPackedAttributes.AllocateOnServer(ctx, pObjects, false);
				mPackedAttributes.Map(ctx, Gl.WRITE_ONLY);
				
				foreach (KeyValuePair<string, InputBinding> k in mAttributes) {
					InputBinding iBinding = k.Value;
					BufferObject iAttributeBuffer = iBinding.Attribute;

					// Copy attribute inteleaving arrays
					iAttributeBuffer.CopyTo(ctx, mPackedAttributes, iInterleaveOffset, iInterleaveStride);
					// Setup offset and stride
					iBinding.InterleaveOffset = (int)iInterleaveOffset;
					iBinding.InterleaveStride = (int)iInterleaveStride;
					// Offset for next attribute
					iInterleaveOffset += iAttributeBuffer.DataBaseTypeSize*iAttributeBuffer.DataBaseArrayLenth;
				}

				mPackedAttributes.Unmap(ctx);
			} else if (mPackBuffers == true) {
				List<BufferObject> pObjects = new List<BufferObject>(mAttributes.Count);
				int pOffset = 0;

				/* Align input bindings */
				foreach (KeyValuePair<string, InputBinding> k in mAttributes) {
					InputBinding iBinding = k.Value;

					// Collect objects
					pObjects.Add(iBinding.Attribute);
					// Setup offset
					iBinding.PackOffset = pOffset;
					// Offset for next attribute
					pOffset += iBinding.Attribute.Size;
				}
				// Pack attributes
				mPackedAttributes = new PackedArrayBufferObject(BufferObject.UsageMode.Draw, BufferObject.XferMode.Static);
				mPackedAttributes.AllocateOnServer(ctx, pObjects, true);
			} else {
				// Allocate each attribute in a separate buffer object
				foreach (KeyValuePair<string, InputBinding> k in mAttributes) {
					string bBindingName = k.Key;
					BufferObject b = k.Value.Attribute;

					Debug.Assert(String.IsNullOrEmpty(bBindingName) == false);

					// Allocate buffer
					if (b.DataSource == BufferObject.Source.Client)
						b.AllocateOnServer(ctx);
				}
			}

			// Allocate element buffer object
			if (mIndices != null) {
				if (mIndices.DataSource == BufferObject.Source.Client)
					mIndices.AllocateOnServer(ctx);
			}
		}

		/// <summary>
		/// Link attribute to a ShaderProgram.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for link shader attributes.
		/// </param>
		/// <param name="sProgram">
		/// A <see cref="ShaderProgram"/> used to link shader attributes.
		/// </param>
		private void LinkAttributes(RenderContext ctx, ShaderProgram sProgram)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (sProgram == null)
				throw new ArgumentNullException("sProgram");

			// Link attributes
			foreach (KeyValuePair<string, InputBinding> k in mAttributes) {
				string bBindingName = k.Key;

				if (sProgram.IsActiveInput(bBindingName) == true) {
					InputBinding iBinding = k.Value;
					int offset = iBinding.PackOffset+iBinding.InterleaveOffset;

					// Link attribute buffer to shader program execution
					sProgram.SetInput(ctx, iBinding.Attribute, bBindingName, iBinding.InterleaveStride, offset);
				} else {
#if DEBUG
					throw new Exception("inactive shader input " + bBindingName);
#endif
				}
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="ctx">
		/// </param>
		/// <param name="sProgram">
		/// </param>
		private void SetShaderAttributes(RenderContext ctx, ShaderProgram sProgram)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (sProgram == null)
				throw new ArgumentNullException("sProgram");
			if (ctx.IsCurrent() == false)
				throw new ArgumentException("ctx.IsCurrent() == false");

			Gl.LogProc("// Setup Shader Inputs");

			#region Vertex Array Generation

			if (ctx.Caps.VertexArrayObject.Supported == true) {
				// Generated vertex array
				if ((mAttributesArray == 0) || (mAttributesChanged == true)) {
					// Delete previous vertex array, if any
					if (mAttributesArray != 0) {
						Gl.DeleteVertexArrays(1, new uint[] { mAttributesArray });
						Debug.Assert(RenderException.CheckErrors() == true);
					}
					// Generate vertex array, if not yet created
					Gl.GenVertexArrays(1, out mAttributesArray);
					Debug.Assert(RenderException.CheckErrors() == true);
				}

				// Apply attribute array state
				Gl.BindVertexArray(mAttributesArray);
				Debug.Assert(RenderException.CheckErrors() == true);
			}

			#endregion

			#region Vertex Array Definition

			// Update attributes binding
			if (mAttributesChanged == true) {
				// Allocate attributes
				AllocateAttributes(ctx);
				// Link attributes
				LinkAttributes(ctx, sProgram);
				// Keep attributes array
				mAttributesChanged = false;
			} else {
				// Link attributes
				if (ctx.Caps.VertexArrayObject.Supported == true)
					LinkAttributes(ctx, sProgram);
			}

			#endregion
		}

		/// <summary>
		/// Input binding.
		/// </summary>
		private class InputBinding
		{
			/// <summary>
			/// Constructor.
			/// </summary>
			/// <param name="name">
			/// A <see cref="System.String"/> that specifies the active input binding.
			/// </param>
			/// <param name="attr">
			/// A <see cref="BufferObject"/> holding the attribute data.
			/// </param>
			public InputBinding(string name, BufferObject attr)
			{
				Debug.Assert(String.IsNullOrEmpty(name) == false);
				Debug.Assert(attr != null);
				
				// Store attribute name
				Name = name;
				// Store attribute data reference
				Attribute = attr;

				// Reference attribute
				Attribute.Ref();
			}

			~InputBinding()
			{
				// Unrference attribute
				Attribute.Unref();
			}

			/// <summary>
			/// ShaderProgram variable name.
			/// </summary>
			public string Name;
			/// <summary>
			/// ShaderProgram data bound to the associated variable.
			/// </summary>
			public BufferObject Attribute;
			/// <summary>
			/// Offset used for accessing to packed buffer object elements.
			/// </summary>
			public int PackOffset = 0;
			/// <summary>
			/// Offset used for accessing to interleaved buffer object elements.
			/// </summary>
			public int InterleaveOffset = 0;
			/// <summary>
			/// Stride used for accessing to interleaved buffer object elements.
			/// </summary>
			public int InterleaveStride = 0;
		}

		/// <summary>
		/// Currently set shader input.
		/// </summary>
		private Dictionary<string, InputBinding> mAttributes = new Dictionary<string, InputBinding>();
		/// <summary>
		/// Attributes packed into a single buffer object.
		/// </summary>
		private PackedArrayBufferObject mPackedAttributes = null;
		/// <summary>
		/// Request to pack buffer objects.
		/// </summary>
		private bool mPackBuffers = false;
		/// <summary>
		/// Request to interleave buffer objects.
		/// </summary>
		private bool mInterleaveBuffers = false;

		/// <summary>
		/// Attributes array object.
		/// </summary>
		private uint mAttributesArray = 0;
		/// <summary>
		/// Attributes list changed flag.
		/// </summary>
		private bool mAttributesChanged = true;

		#endregion

		#region Attributes Indices

		/// <summary>
		/// Setup attribute indices.
		/// </summary>
		/// <param name="ibo">
		/// An <see cref="Derm.Render.ElementBufferObject{UInt32}"/> used for rendering.
		/// </param>
		public void SetIndices(ElementBufferObject<uint> ibo)
		{
			SetIndices(ibo, 0);
		}

		/// <summary>
		/// Setup attribute indices.
		/// </summary>
		/// <param name="ibo">
		/// An <see cref="Derm.Render.ElementBufferObject{UInt32}"/> used for rendering.
		/// </param>
		/// <param name="offset">
		/// An <see cref="System.Int32"/> used as offset for accessing to the indices
		/// buffer object.
		/// </param>
		public void SetIndices(ElementBufferObject<uint> ibo, int offset)
		{
			Debug.Assert((offset == 0) || (offset < ibo.ItemsCount));
			Debug.Assert(offset >= 0);

			// Store attributes indices
			mIndices = ibo;
			// Reset offset
			mIndicesOffset = offset;
		}

		/// <summary>
		/// Setup an element index used for restarting primitive rendering.
		/// </summary>
		/// <param name="enable">
		/// A <see cref="System.Boolean"/> indicating whether primitive restarting
		/// is enabled or not.
		/// </param>
		/// <param name="index">
		/// A <see cref="System.UInt32"/> indicating the element index value used for
		/// restarting a primitive. Usually this value is set to 0xFFFFFFFF, but it can
		/// be any arbitrary value.
		/// </param>
		public void SetIndicesRestart(bool enable, uint index)
		{
			Debug.Assert(mIndices != null);

			// Restart element flag
			mRestartElement = enable;
			// Index specifying primitive restart
			mRestartIndex = index;
		}

		/// <summary>
		/// Element indices attribute
		/// </summary>
		public ElementBufferObject<uint> Indices
		{
			get {
				return (mIndices);
			}
		}

		/// <summary>
		/// Attribute indices buffer.
		/// </summary>
		private ElementBufferObject<uint> mIndices = null;
		/// <summary>
		/// Attribute indices buffer offset.
		/// </summary>
		private int mIndicesOffset = 0;

		/// <summary>
		/// Primitive restarting enabled flag.
		/// </summary>
		private bool mRestartElement = false;
		/// <summary>
		/// Restart element index.
		/// </summary>
		private uint mRestartIndex = 0;

		#endregion

		#region Attribute Packing & Interleaving

		/// <summary>
		/// Pack ShaderAttributes buffers currently set.
		/// </summary>
		public void PackAttributes()
		{
			// Request to interleave attributes
			mPackBuffers = true;
			mInterleaveBuffers = false;
			// Reallocate attributes
			mAttributesChanged = true;
		}

		/// <summary>
		/// Inteleave ShaderAttributes buffers currently set.
		/// </summary>
		public void InterleaveAttributes()
		{
			// Request to pack attributes
			mInterleaveBuffers = true;
			mPackBuffers = false;
			// Reallocate attributes
			mAttributesChanged = true;
		}

		#endregion

		#region Attributes Rendering

		/// <summary>
		/// Attributes drawing mode.
		/// </summary>
		public Primitive DrawMode
		{
			get {
				switch (mDrawMode) {
					case Gl.POINTS:
						return (Primitive.Point);
					case Gl.LINES:
						return (Primitive.Line);
					case Gl.LINE_STRIP:
						return (Primitive.LineStrip);
					case Gl.LINE_LOOP:
						return (Primitive.LineLoop);
					case Gl.TRIANGLES:
						return (Primitive.Triangle);
					case Gl.TRIANGLE_STRIP:
						return (Primitive.TriangleStrip);
					case Gl.TRIANGLE_FAN:
						return (Primitive.TriangleFan);
					default:
						return (Primitive.Point);
				}
			}
			set {
				switch (value) {
					case Primitive.Point:
						mDrawMode = Gl.POINTS;
						break;
					case Primitive.Line:
						mDrawMode = Gl.LINES;
						break;
					case Primitive.LineStrip:
						mDrawMode = Gl.LINE_STRIP;
						break;
					case Primitive.LineLoop:
						mDrawMode = Gl.LINE_LOOP;
						break;
					case Primitive.Triangle:
						mDrawMode = Gl.TRIANGLES;
						break;
					case Primitive.TriangleStrip:
						mDrawMode = Gl.TRIANGLE_STRIP;
						break;
					case Primitive.TriangleFan:
						mDrawMode = Gl.TRIANGLE_FAN;
						break;
				}
			}
		}

		/// <summary>
		/// Buffer attributes count.
		/// </summary>
		/// <remarks>
		/// This property specify how many vertex attributes are executed on a shader program. If
		/// this property is 0, no attributes are executed. It cannot be negative.
		/// </remarks>
		protected int ItemsCount
		{
			get { return (mCount); }
			set {
				Debug.Assert(value >= 0);
				mCount = value;
			}
		}

		/// <summary>
		/// Generated primitive instances.
		/// </summary>
		protected int Instances
		{
			get { return (mInstances); }
			set {
				Debug.Assert(value >= 0);
				mInstances = value;
			}
		}

		/// <summary>
		/// Attributes draw mode.
		/// </summary>
		private int mDrawMode = Gl.POINTS;

		/// <summary>
		/// Vertex attribute count.
		/// </summary>
		private int mCount = 0;

		/// <summary>
		/// Draw vertices instancing them.
		/// </summary>
		private int mInstances = 0;

		#endregion

		#region BaseShaderAttributes Overrides

		/// <summary>
		/// Current attributes bindings.
		/// </summary>
		public override IEnumerable<string> Bindings
		{
			get {
				List<string> bindings = new List<string>(mAttributes.Count);

				foreach (KeyValuePair<string, InputBinding> k in mAttributes)
					bindings.Add(k.Key);

				return (bindings);
			}
		}

		/// <summary>
		/// Render this ShaderAttributes.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for rendering.
		/// </param>
		/// <param name="sProgram">
		/// The <see cref="ShaderProgram"/> used for rendering attributes.
		/// </param>
		/// <remarks>
		/// <para>
		/// This routine draw active vertices attribute with the currently bound shader program. The number
		/// of vertices drawn is set by <see cref="ShaderAttributes.ItemsCount"/>, and the resulting primitive
		/// is set by <see cref="ShaderAttributes.DrawMode"/>.
		/// </para>
		/// <para>
		/// Optionally the drawing command can be instanced more times, by setting the property
		/// <see cref="ShaderAttributes.Instances"/> a value greater than zero. Each drawing command instance will
		/// have set an incremental value of the shader built-in variable <i>gl_instanceID</i>.
		/// </para>
		/// <para>
		/// Optionally an offset could be applied when drawing vertex attributes. To reduce memory fragmentation
		/// caused by usage of many (and relatively small) buffer objects, implementations could choose to "pack"
		/// multiple buffer objects (having a common state) into a single (larger) buffer object; when rendering,
		/// drawing commands could specify the offset to access to vertex attributes.
		/// In the case drawing is executed using indices, the offset is applied to indices during rendering, without
		/// altering the original indices buffer contents.
		/// </para>
		/// </remarks>
		public override void Render(RenderContext ctx, ShaderProgram sProgram)
		{
			Gl.LogMethod(MethodInfo.GetCurrentMethod(), ctx, sProgram);

			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (sProgram == null)
				throw new ArgumentNullException("sProgram");
			if (sProgram.IsLinked == false)
				throw new ArgumentException("sProgram.IsLinked == false");

			// Setup shader attributes
			SetShaderAttributes(ctx, sProgram);

			// Draw attributes
			if (mIndices == null) {
				// Draw arrays
				if (mInstances == 0)
					Gl.DrawArrays(mDrawMode, 0, mCount);
				else
					Gl.DrawArraysInstanced(mDrawMode, 0, mCount, mInstances);
			} else {
				// Restart primitive, if required
				if (mRestartElement == true) {
					// Enable primitive restart
					Gl.Enable(Gl.PRIMITIVE_RESTART);
					Debug.Assert(RenderException.CheckErrors() == true);
					// Set primitive restart index
					Gl.PrimitiveRestartIndex(mRestartIndex);
					Debug.Assert(RenderException.CheckErrors() == true);
				}
				// Bind indices
				mIndices.Bind(ctx);

				// Draw elements
				if (mInstances == 0) {
					Gl.DrawElements(mDrawMode, mCount, Gl.UNSIGNED_INT, null);
				} else {
					Gl.DrawElementsInstanced(mDrawMode, mCount, Gl.UNSIGNED_INT, null, mInstances);
				}

				// Unbind indices
				mIndices.Unbind(ctx);
				// Disable restart primitive
				if (mRestartElement == true) {
					Gl.Disable(Gl.PRIMITIVE_RESTART);
					Debug.Assert(RenderException.CheckErrors() == true);
				}
			}
			Debug.Assert(RenderException.CheckErrors() == true);
		}

		#endregion
	}

	/// <summary>
	/// Rendered attribute buffers proxy.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class is dedicated to supply data to ShaderProgram instances, and execute
	/// ShaderProgram instances based on the input set. The difference between this class
	/// and the ShaderAttributes class is that this class reference an already existing
	/// ShaderAttributes instance.
	/// </para>
	/// <para>
	/// This class allow to reference an already existing ShaderAttributes class, without
	/// duplicating the buffers.
	/// </para>
	/// </remarks>
	public class ShaderAttributesProxy : BaseShaderAttributes
	{
		#region Constructors

		/// <summary>
		/// Construct a ShaderAttributesProxy targeting a ShaderAttributes instance.
		/// </summary>
		/// <param name="target">
		/// A <see cref="ShaderAttributes"/> which are really used for rendering.
		/// </param>
		public ShaderAttributesProxy(ShaderAttributes target)
		{
			Debug.Assert(target != null);

			mAttributesProxy = target;
		}

		/// <summary>
		/// Shader attributes proxy target
		/// </summary>
		private ShaderAttributes mAttributesProxy;

		#endregion

		#region BaseShaderAttributes Overrides

		/// <summary>
		/// Current attributes bindings.
		/// </summary>
		public override IEnumerable<string> Bindings
		{
			get {
				return (mAttributesProxy.Bindings);
			}
		}

		/// <summary>
		/// Render this ShaderAttributes.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for rendering.
		/// </param>
		/// <param name="sProgram">
		/// The <see cref="ShaderProgram"/> used for rendering attributes.
		/// </param>
		/// <remarks>
		/// This method renders the <see cref="ShaderAttributes"/> specified at construction time.
		/// For more information, <see cref="ShaderAttributes.Render"/>.
		/// </remarks>
		public override void Render(RenderContext ctx, ShaderProgram sProgram)
		{
			// Recall rendering using target
			mAttributesProxy.Render(ctx, sProgram);
		}

		#endregion
	}
}